/* ----------------------------------------------------------------------------------
NAME
     Scroll

DESCRIPTION
      This code demonstrates how to use scroll bars.
      You can scroll text or graphics or both.
      You can scroll horizontally or vertically.

AUTHOR
      Cary Clark, Macintosh Technical Support
      Copyright 1984 Apple Computer Inc.

---------------------------------------------------------------------------------- */

#include "mac/c/include/interface.h"   /* Macintosh definitions and functions */

#define Horizontal    1    /* These are the choices in the menu "Scroll Bar". */
#define Vertical      2
#define TextItem      4
#define Graphics      5

#define FileMenu      1    /* resource numbers and position in the Menu bar */
#define ScrollMenu    2

#define NumOfRects   30    /* quantity of rectangles and strings to scroll around */
#define NumOfStrings 55

/* The graphics scrolled in this example are an array of rectangles. */
typedef Rect MyRectData[NumOfRects], (**MyRectHdl)[NumOfRects]; /*  */


TEHandle       hTE;                /* TextEdit handle */
ControlHandle  hScroll,            /* horizontal scroll bar */
               vScroll;            /* vertical scroll bar */
WindowPtr      myWindow;           /* document window */
MenuHandle     hdlScrollMenu;      /* handle to the menu items */
MyRectHdl      myRect;             /* handle to array of rectangles */
short          originalPart;       /* 1st part of the scroll bar hit */
Point          pageCorner,         /* location of the upper left hand page corner */
               eventPoint;         /* where an event took place */
Rect           growBoxRect,        /* area of the window reserved for the grow box */
               myViewRect;         /* display rectangle containing scrollable data */
Boolean        doneFlag = false,   /* set true when the user selects 'Quit' */
               showText = true,    /* set true when text can be scrolled */
               showGraphics = false;   /* set true when graphics can be scrolled */

SetUpData() /* ------------------------------------------------------------------- */

/* This procedure initializes two data structures; a TextEdit record and an array of
rectangles.  Initially, only text and the vertical scrollbar will be displayed. */
{
   StringHandle myString; /* temporary container for a string in the resource fork */
   short counter;         /* counters must be local to the procedure */
   Rect destRect;         /* rectangle containing the larger-than-the-screen page */


/* The TextEdit record is initialized by reading in a string from the application's
resource fork and then inserting it a number of times into the TextEdit record. */

   myString = GetString(256); /* Get some text to play around with. */

/* Set the view as the portrect less the vertical scrollbar area.  The TextEdit
destRect will be set to the current window width plus an arbitrary value. */

   myViewRect = myWindow->portRect;
   destRect = myViewRect;
   destRect.right += 300;
   pageCorner.h = -destRect.left;
   pageCorner.v = -destRect.top;
   myViewRect.right -= 15; /* subtract width of scrollbar */
   hTE = TENew(&destRect, &myViewRect);

   HLock(myString);       /* can't move if we are going to point to the text */
/* Create a TextEdit record full of the string. */
   for (counter = 1; counter <= NumOfStrings; ++counter)
      TEInsert(&(**myString).text[0], (**myString).length, hTE);
   HUnLock(myString);    /* free to move again */

/* Now, create a structure of rectangles. */
   myRect = (MyRectHdl) NewHandle(sizeof(MyRectData));  /* 240 bytes */
   for (counter = 1; counter <= NumOfRects; ++counter)
      SetRect(&(**myRect)[counter - 1], counter * 23, counter * 20, counter * 23 + 50,
       counter * 20 + 50);

   ShowWindow(myWindow); /* Display the window and the text it contains */

   vScroll = GetNewControl(256, myWindow); /* vertical scrollbar */
   hScroll = GetNewControl(257, myWindow); /* horizontal scrollbar, not shown */
   SetRect(&growBoxRect, (**vScroll).contrlRect.left+1,
    (**vScroll).contrlRect.bottom+1, myWindow->portRect.right,
    myWindow->portRect.bottom); /* This area is set up for ValidRect, below. */

   CheckItem(hdlScrollMenu, Vertical, true);
   CheckItem(hdlScrollMenu, TextItem, true);
}   /* end of SetUpData */


GrafUpdate(whatpart) /* ---------------------------------------------------------- */
   Rect whatpart;
/* This is roughly the equivalent of what TEUpdate does with text.  The upper left
hand corner of the page is moved up and to the left to simulate a view further down
and to the right.  To more accurately resemble a Toolbox routine like TEUpdate, this
procedure should also preserve the current clip region and origin. */
{
   short count;
   Rect dummyRect;

   SetOrigin(pageCorner.h, pageCorner.v); /* negative moves the origin left, up */
   OffsetRect(&whatpart, pageCorner.h, pageCorner.v); /* also move update rect */
   ClipRect(&whatpart); /* only redraw the portion that the user requests */
   for (count = 0; count < NumOfRects; ++count)
/* Redraw the object if it intersects the update rectangle */
      if (SectRect(&(**myRect)[count], &whatpart, &dummyRect))
         FrameRect(&(**myRect)[count]);
   SetOrigin(0,0); /* reset the origin back to the upper left hand corner */
   ClipRect(&myWindow->portRect); /* reset the clip region to the entire window */
} /* end of GrafUpdate */


ScrollBits() /* ------------------------------------------------------------------ */
/* This routine scrolls horizontally and vertically both graphics and text.  If you
are only scrolling text, only the TEScroll is required.  If you are only scrolling
graphics, then only the ScrollRect and GrafUpDate is needed. */

{
   short     vChange,
             hChange,
             vScrollValue,
             hScrollValue;
   RgnHandle anUpdateRgn;

   vScrollValue = GetCtlValue(vScroll); /* These values will be used a lot so they */
   hScrollValue = GetCtlValue(hScroll); /* are read into temporary variables. */

/* Find the vertical and horizontal change. */
   vChange = pageCorner.v - vScrollValue; /* the vertical difference */
   hChange = pageCorner.h - hScrollValue; /* the horizontal difference */

/* Record the values for next time */
   pageCorner.v = vScrollValue;
   pageCorner.h = hScrollValue;

/* For pure text, only a TEScroll is required. */
   if (!showGraphics && showText) TEScroll(hChange, vChange, hTE);

/* For graphics, a ScrollRect will move the visible bits on the screen.  The
  region returned by ScrollRect indicates what part of the window needs to
  be updated. */
   if (showGraphics) {
      anUpdateRgn = NewRgn();
      ScrollRect(&myViewRect, hChange, vChange, anUpdateRgn);

/* This draws the new text.  The clipping is necessary because normally
  TextEdit redraws the entire character height and perhaps only a partial
  character scroll was done.  Since TextEdit erases before it draws, the text,
  if any, is drawn before the graphics. */
      if (showText) {
         (**hTE).destRect.left = -hScrollValue;
         (**hTE).destRect.top = -vScrollValue;
         ClipRect(&(**anUpdateRgn).rgnBBox);
         TEUpdate(&(**anUpdateRgn).rgnBBox, hTE);
         ClipRect(&(myWindow->portRect));
      } /* end of showText */

      GrafUpdate((**anUpdateRgn).rgnBBox); /* Fill in the newly exposed region */
      DisposeRgn(anUpdateRgn);
   } /* end of showGraphics */
} /* end of ScrollBits */

pascal void TrackScroll(theControl, partCode) /*---------------------------------- */
   ControlHandle    theControl;
   short            partCode;
/*This routine adjusts the value of the scrollbar.  A reasonable change would
be to adjust the minimum scroll amount to equal the text's lineheight. */
{
   short            amount,
                    startValue;
   Boolean          up;

   up = (partCode == inUpButton) || (partCode == inPageUp); /*true if scrolling up */
   startValue = GetCtlValue(theControl);  /*the initial control value */

   if /*the scrollbar value is decreased, and it is not already at the minimum */
    ( ((up && (startValue > GetCtlMin(theControl)))
    || /*the scrollbar value is increased, and it is not already at the maximum */
    ((! up) && (startValue < GetCtlMax(theControl))))
    && /* to prevent tracking if the page up or down area disappears */
    (originalPart == partCode) ) {
      if (up)
         amount = -1;
      else
         amount = 1;  /* Set the direction. */
      if ((partCode == inPageUp) || (partCode == inPageDown)) {
/* Change the movement to a full page. */
         if (theControl == vScroll)
            amount *= myViewRect.bottom - myViewRect.top;
         else
            amount *= myViewRect.right - myViewRect.left;
      } /* end of partCode */
      SetCtlValue(theControl, startValue + amount);
      ScrollBits();
   }
} /* end of TrackScroll */

MyControls() /* ------------------------------------------------------------------ */
/* respond to a mouse down event in one of the controls */
{
   short          dummy;
   ControlHandle  theControl;

/* return control and part */
   originalPart = FindControl(&eventPoint, myWindow, &theControl);
   if (originalPart == inThumb) {
/* The thumb is tracked until it is released; then the bits are scrolled. */
      dummy = TrackControl(theControl, &eventPoint, nil);
      ScrollBits();
   } /* end of whichpart */
/* For the arrows and the page changes, scroll while the mouse is held down. */
   else dummy = TrackControl(theControl, &eventPoint, TrackScroll);
} /* end of MyControls */

MainEventLoop() /*---------------------------------------------------------------- */
/* respond to menu selections, the scrollbars, and update events */
{
   EventRecord myEvent;                /* all of the information about the event */
   int         menuResult;             /* information returned by MenuSelect */
   short       theMenu,                /* which menu was selected */
               theItem;                /* which item within the menu */
   Boolean     checked;                /* Is the menu item checked? */
   short       markChar;               /* the checkmark character */
   WindowPtr   tempWindow;
   Rect        tempRect;

   do {
      checked = GetNextEvent(everyEvent,&myEvent); /*checked here is ignored */
      switch(myEvent.what) {
      case mouseDown: /* The user pressed or is holding the mouse button down. */
         switch(FindWindow(&myEvent.where,&tempWindow)) {
         case inMenuBar: /*the mouseDown was in the menu bar */
            menuResult = MenuSelect(&myEvent.where);
            theMenu = HiWord(menuResult);
            theItem = LoWord(menuResult);
            switch(theMenu) {
               case FileMenu:
                  doneFlag = true;    /* Quit  */
                  break;
               case ScrollMenu:
/* The items in the menu are used to keep track of the user has chosen thus far.
These lines toggle the checkmark in the menu and leave the result in the variable
'checked'. */
                  GetItemMark(hdlScrollMenu, theItem, &markChar);
                  checked = (markChar != checkMark);
                  CheckItem(hdlScrollMenu, theItem, checked);

/* Any selection will cause some part of the screen to be redrawn.  The selection
that the user makes causes some part of the screen to become invalid. */
                  if ((theItem == TextItem) || (theItem == Graphics)) {
                     InvalRect(&myViewRect);
/* The small area between the scrollbars reserved for the grow box should never be
redrawn. */
                     ValidRect(&growBoxRect);
                  }
                  switch(theItem) {
                  case Horizontal:
                     InvalRect(&(**hScroll).contrlRect);
                     if (checked) {
                        ShowControl(hScroll);
                        myViewRect.bottom = (**hScroll).contrlRect.top;
                     } /* end of checked */
                     else { /* not checked */
                        HideControl(hScroll);
                        myViewRect.bottom = (**hScroll).contrlRect.bottom;
                     } /* end of not checked */
                     break; /* end of horizontal */

                  case Vertical:
                     InvalRect(&(**vScroll).contrlRect);
                     if (checked) {
                        ShowControl(vScroll);
                        myViewRect.right = (**vScroll).contrlRect.left;
                     } /* end of checked */
                     else { /*not checked */
                        HideControl(vScroll);
                        myViewRect.right = (**vScroll).contrlRect.right;
                     } /* end of not checked */
                     break; /* end of vertical */

                  case TextItem:
/* Since we dereference the destRect into tempRect, no calls in the scope of this
statement should cause a memory compaction. */
                     showText = checked;
                     if (checked) {
                        (**hTE).destRect.top = -GetCtlValue(vScroll);
                        (**hTE).destRect.left = -GetCtlValue(hScroll);
                     } /* end of checked */
                     break; /* end of textItem */

                  case Graphics:
                     showGraphics = checked;
                     break;
                  } /* end of theItem switch */
               if (showText)
                  (**hTE).viewRect = myViewRect;
               break; /* end of case ScrollMenu */
            } /* end of theMenu switch */
            HiliteMenu(0);
            break; /* end of case inMenuBar: */

         case inContent:
/* Rectangles making up controls are the part of the window outside the 'view'. */
            eventPoint = myEvent.where;
            GlobalToLocal(&eventPoint);
            if (!PtInRect(&eventPoint, &myViewRect))
              MyControls();
            break; /* end of case inContent: */
         } /* end of FindWindow switch */
         break; /* end of case mouseDown: */

         case updateEvt:
/* In response to InvalRects, the appropriate text or graphics is erased and redrawn.
The BeginUpdate causes the VisRgn to be replaced by the intersection of the VisRgn
and the UpdateRgn. */
            BeginUpdate(myWindow);
            EraseRect(&myViewRect); /*start with a clean slate */
            if (showText)
               TEUpdate(&(**(myWindow->visRgn)).rgnBBox, hTE);
/* Call GrafUpdate with the intersection, if any, of the VisRgn and the view */
            if (showGraphics && SectRect(&(**myWindow->visRgn).rgnBBox, &myViewRect,
             &tempRect))
               GrafUpdate(tempRect);
            EndUpdate(myWindow);
            break; /* end of case updateEvent: */
         } /* end of myEvent.what switch */
      } /* end of do */
   while(!doneFlag);
} /* end of event loop */


main() /* ------------------------------------------------------------------------ */
/* main program */
{
   InitGraf(&qd.thePort); /* Initialize QuickDraw. */
   InitWindows();         /* Initialize Window Manager; clear desktop and menubar. */
   InitFonts();           /* Initialize Font Manager. */
   FlushEvents(everyEvent,0); /* Throw away any stray events. */
   TEInit();              /* Initialize TextEdit. */
   InitMenus();           /* Initialize Menu Manager. */
   hdlScrollMenu = GetMenu(FileMenu); /* (hdlScrollMenu is ignored.) */
   InsertMenu(hdlScrollMenu,0);
   hdlScrollMenu = GetMenu(ScrollMenu);
   InsertMenu(hdlScrollMenu,0);
   DrawMenuBar();
   myWindow = GetNewWindow(256, nil, (WindowPtr) -1); /* get window to work within */
   SetPort(myWindow);     /* Point to a window. */
   TextFont(applFont);    /* Select default application font. */
   SetUpData();           /* Initialize user data and controls. */
   InitCursor();          /* Change the watch into an arrow. */
   MainEventLoop();       /* Handle events until we are through. */
}
